UserScriptをbundleするDeno script
2024-01-09 12:11:41 deno bundleの代替コードを推奨
主にScrapboxのUserScriptを一つのJSファイルにまとめるDeno script
一般的なbundleにも使える
一部のESModuleをまとめないでimportのままにする事もできる
既に自分のprojectにあるコードをimport先にすることができる
用途
たくさんのESModuleに分かれた他者のUserScriptを自分のprojectに移植するときに使う
すべてのファイルを手作業でコピペするのは地獄なので、一つにまとめたほうがいい
2021-06-08 22:21:21 script読み込みの高速化以外にメリットなくね?takker.icon
確かに自分のprojectにコピペすれば、そのscriptに誰かが悪意のあるコードを入れられなくなるが、すでに悪意のあるコードが混入している場合には無力
1ファイルずつ、自分で変なコードが混じっていないか確認する必要がある
既知の問題
dynamic importに相対パスを使っているとうまくコードが動かなくなる
workerのソースも同様
scrapboxのUserScriptの場合、../や/api/codeから始まるパスなら大丈夫
./で同じページ内のファイルを指定していたときに事故る
tree shakingがうまく働かない?
deno bundleだと↓はうまくコードを省いてくれるが、本ページのscriptだと余計なものまで含まれてしまう
code:script.js
export {lightFormat, sub, getUnixTime} from 'https://deno.land/x/date_fns@v2.15.0/index.js';
code:importmap.json
{
"imports": {
"../date-fns.min.js/script.js": "https://deno.land/x/date_fns@v2.15.0/index.js"
}
}
いやちゃんとtree shakingされた
気のせいだった?
reloadしなかったせい?
使い方
以下を実行する
code:sh
deno run -A --unstable https://scrapbox.io/api/code/takker/UserScriptをbundleするDeno_script/build.ts filename.js --bundle --minify --charset=utf8 --outfile=filename.min.js --external=xxx --external=yyy --importMap=map.json
コマンドライン引数はesbuildのものを流用できる
filename.js
bundleしたいscriptのentry point
localのファイルでもインターネット上のファイルでも可
--outfile
bundle後のJSファイルのファイル名
指定しないとコードが標準出力に出力される
パイプを使って | pbcopyや | xselをつなげれば、そのままクリップボードにコピーできる
--external
bundleに含めたくないESModuleのパス名
JSファイルに書き込まれているパス名をそのまま書く
相対パスで指定されていたら相対パスで、絶対パスで指定されていたら絶対パスで書く
挙動はただの文字列判定
import {...} from 'xxx'のxxxが--externalに指定した文字列と一致した場合、そのmoduleをbundleから除外する
--importMap
import mapを指定できる
localファイルでもインターネット上のファイルでも可
あとはesbuildの引数と同じ
source mapを生成したいときは--sourcemapをつける
JSファイルに埋め込むときは--sourcemap=inlineにする
source mapを生成しておくと、元の変数名を参照できるのでデバッグが楽になる
関連
UserScriptをbundleするDeno script実装案
UserScriptをbundleするテスト
/programming-notes/UserScriptをbundle&minifyするUserScript
実装したいこと
denomanderで親切なヘルプを作りたい
esbuildのオプション引数を全部羅列するのが面倒か
2022-02-01
13:52:06 コード雑だな……
直したいtakker.icon
2021-06-05
12:45:46
build errorが出てもcacheを消す
refactor: 型をつける
12:03:40
APIとCLIを分離した
esbuildのversionを固定した
2021-05-28
22:36:22 esbuildのcache directoryを消すようにした
2021-05-26
18:17:27 --import-mapを--importMapにした
18:08:33 --externalが一つのときにバグっていたのを直した
code
code:build.ts
import { run } from './script.ts';
import { parse } from 'https://deno.land/std@0.87.0/flags/mod.ts';
const {_: entryFilePath, importMap, external, ...rest} = parse(Deno.args);
if (typeof entryFilePath === 'number') throw Error('entryFilePath must be string');
let imports: {key: string: string} = {};
if (importMap) {
if (/^https?:\/\//.test(importMap)) {
const res = await fetch(importMap);
imports = await res.json();
} else {
imports = JSON.parse(await Deno.readTextFile(importMap));
}
}
await run(entryFilePath, imports, {external, ...rest});
TypeScript型から特定のpropertiesを削除
code:script.ts
import { exists } from "https://deno.land/std@0.97.0/fs/mod.ts";
import { build, stop } from 'https://deno.land/x/esbuild@v0.12.6/mod.js';
import type { BuildOptions, BuildResult } from 'https://deno.land/x/esbuild@v0.12.6/mod.js';
import { cache } from 'https://raw.githubusercontent.com/takker99/esbuild-plugin-cache/master/deno/mod.ts';
// 特定のpropertyを削るやつ
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export async function run(filename: string, imports: {key: string: string}, {external, ...rest }: Omit<BuildOptions, 'entryPoints' | 'platform' | 'plugins'>) {
let useTempFile = false;
let result: BuildResult | undefined = undefined;
try {
if (/^https?:\/\//.test(filename)) {
const tempname = index-${Math.random()}.ts;
await Deno.writeTextFile(tempname, import '${filename}';export * from '${filename}';);
filename = tempname;
useTempFile = true;
}
const options: BuildOptions = {
entryPoints: filename,
platform: 'neutral',
plugins: cache({directory: './cache', importmap: {imports}}),
external: Array.isArray(external) ? external : (external ? external : []),
...rest
};
result = await build(options);
stop();
} catch(e) {
throw e;
}finally {
//後始末
if (useTempFile) await Deno.remove(filename);
if (await exists('./cache')) await Deno.remove('./cache', { recursive: true });
}
return result;
}
#2024-01-09 12:12:07
#2022-02-01 13:58:24
#2021-08-25 14:14:00
#2021-07-06 06:07:05
#2021-06-26 23:49:37
#2021-06-08 22:23:02
#2021-06-05 13:15:59
#2021-05-28 22:02:52
#2021-05-26 16:00:52
#2021-05-25 06:11:06